import random
import uuid
from datetime import datetime, timedelta

import bcrypt
from django.db import transaction
from django.db.models import Avg, Q
from requests import RequestException

from gis.admin.decorators import list_func
from gis.common import track_logging
from gis.common.django_ext.models import paginate
from gis.common.exceptions import BizException
from gis.common.retry import retry
from itpay.core.agency import (
    agency_stat_service,
    blacklist_service,
    agency_sort_service,
)
from itpay.core.agency.const import (
    AgencyType,
    COMPLAIN_HANDLER_MSG,
    ComplaintTypeCN,
    AgencySortScoreType,
    AGENCY_SCORE_LEVEL,
)
from itpay.core.agency.errors import (
    ERROR_AGENCY_NOT_EXISTS,
    ERROR_USER_DISABLED,
    ERROR_USER_NAME_DUPLICATE,
    ERROR_USER_TOKEN_NOT_EXISTS,
    ERROR_NORMAL_AGENCY_NOT_HAVE_PARENT,
    ERROR_BALANCE_NOT_ENOUGH_GO_TO_WORK,
    ERROR_COMPLAIN_STATUS,
    ERROR_COMPLAIN_NOT_EXISTS,
    ILLEGAL_PARAMETER,
    ERROR_USER_PASSWORD_INCORRECT,
    ERROR_AGREE_TYPE,
    ERROR_HAD_COMPLAIN_TODAY,
)
from itpay.core.conf import IMConf
from itpay.core.mch import service as mch_service
from itpay.core.recharge_order import order_service
from itpay.core.recharge_order import recharge_comment_service
from itpay.core.recharge_order.models import Comment
from itpay.core.user import user_service
from itpay.core.user.const import UserLevel
from itpay.utils import rpc, maestro_tracker
from .models import Account, Token, Complaint

_LOGGER = track_logging.getLogger(__name__)


def _get_agency_account(agency_id, with_lock=False):
    if with_lock:
        user = Account.objects.select_for_update().filter(pk=agency_id).first()
    else:
        user = Account.objects.filter(pk=agency_id).first()
    if not user:
        raise BizException(ERROR_AGENCY_NOT_EXISTS, agency_id)
    return user


def _get_user_by_name(user_name, check_enable=True):
    user = Account.objects.filter(user_name=user_name).first()
    _check_user_valid(user, check_enable)
    return user


def _check_user_valid(user, check_enable):
    if not user:
        raise BizException(ERROR_AGENCY_NOT_EXISTS)
    if check_enable and not user.enable:
        raise BizException(ERROR_USER_DISABLED)


def verify_password(name, password):
    """
    验证密码正确性
    """
    user = _get_user_by_name(name)
    if not user:
        raise BizException(ERROR_AGENCY_NOT_EXISTS)
    if not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
        raise BizException(ERROR_USER_PASSWORD_INCORRECT)
    return user.to_dict(exclude=["password"])


def login(user_name, password):
    user = verify_password(user_name, password)
    # Token.objects.filter(user_id=user['id']).delete()

    new_token = str(uuid.uuid4())
    Token.objects.create(user_id=user["id"], token=new_token)
    user.update({"token": new_token})

    return user


def logout(user_id, token):
    Token.objects.filter(user_id=user_id, token=token).delete()


def check_user_name_duplicate(user_name):
    if Account.objects.filter(user_name=user_name).exists():
        raise BizException(ERROR_USER_NAME_DUPLICATE)


def check_agency_parent(agency_id, parent_id):
    return Account.objects.filter(id=agency_id, parent_id=parent_id).exists()


def get_agency_by_id(agency_id, with_mch_info=False):
    agency = _get_agency_account(agency_id)
    return agency.to_dict(exclude=["password"], return_many_to_many=with_mch_info)


def get_agencys_by_ids(agency_ids):
    agencys = Account.objects.filter(id__in=agency_ids)
    return [agency.to_dict(exclude=["password", "im_token"]) for agency in agencys]


def get_agency_by_token(token):
    token = Token.objects.filter(
        token=token,
        created_at__gt=datetime.now() - timedelta(hours=6),
        user__enable=True,
    ).first()
    if not token:
        raise BizException(ERROR_USER_TOKEN_NOT_EXISTS)
    return token.user.to_dict(exclude=["password"])


def get_agency_model_with_lock(agency_id):
    return _get_agency_account(agency_id, with_lock=True)


def create_agency(
    user_name,
    agency_type,
    parent_id=None,
    avatar=None,
    use_vcode=None,
    warning_amount=None,
    remark=None,
    mch_ids=None,
    white_ips=None,
    enable=None,
):
    assert user_name
    assert isinstance(agency_type, AgencyType)
    if agency_type == AgencyType.NORMAL_AGENCY and not parent_id:
        raise BizException(ERROR_NORMAL_AGENCY_NOT_HAVE_PARENT)

    check_user_name_duplicate(user_name)
    password = str(uuid.uuid4())
    encrypted_pwd = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode(
        "utf-8"
    )

    with transaction.atomic():
        agency = Account.objects.create(
            user_name=user_name,
            password=encrypted_pwd,
            agency_type=agency_type.value,
            parent_id=parent_id,
            avatar=avatar,
            remark=remark,
            enable=enable,
            white_ips=white_ips,
            nickname=user_name,
            auto_show_level=UserLevel.LEVEL_0.value,
        )
        if use_vcode is not None:
            agency.use_vcode = use_vcode
        if warning_amount is not None:
            agency.warning_amount = warning_amount
        agency.save()

        mch_service.bind_mch_to_agency(agency, mch_ids)
        agency_dict = agency.to_dict()
        agency_dict.update({"password": password})
        # 创建关联统计表
        agency_stat_service.create_stat(agency.id)

    # 注册im
    try:
        rpc_register_im(agency.id)
    except Exception:
        _LOGGER.exception("register im fail")

    return agency_dict


@retry(RequestException, tries=2)
def rpc_register_im(agency_id):
    m_agency = _get_agency_account(agency_id)
    params = {
        "app_id": IMConf.app_id,
        "user_id": agency_id,
        "nickname": m_agency.nickname if m_agency.nickname else None,
        "portrait_uri": (IMConf.img_host + m_agency.avatar)
        if m_agency.avatar
        else None,
        "im_leaving_time": m_agency.im_leaving_time,
    }

    resp = rpc.do_post(IMConf.host + "open/get_token/", params, IMConf.api_key)
    if resp["status"] != 0:
        raise Exception("fail register im: {}".format(agency_id))
    m_agency.im_id = resp["data"]["id"]
    m_agency.im_token = resp["data"]["token"]
    m_agency.save()


@retry(RequestException, tries=2)
def rpc_complain_handler(complain_id):
    """
    远程调用 通知im 投诉结果
    """
    complain = _get_complain(complain_id)

    params = {
        "app_id": IMConf.app_id,
        "complain_id": complain_id,
        "play_mch_id": complain.mch_id,
        "player_id": int(complain.user_id),
        "agency_id": complain.agency_id,
        "message": COMPLAIN_HANDLER_MSG.format(
            created_at=complain.created_at.strftime("%Y-%m-%d %H:%M:%S"),
            agency_name=complain.agency.nickname,
            complain_type=ComplaintTypeCN[complain.complaint_types[0]],
            complain_content=complain.complaint_content,
            remark=complain.remark,
        ),
    }
    _LOGGER.info("rpc_complain_handler params {}".format(params))
    rpc.do_post(IMConf.host + "open/pay/complain/notify/", params, IMConf.api_key)


def reset_password(agency_id, password=None):
    assert agency_id
    agency = _get_agency_account(agency_id)
    if not password:
        password = str(uuid.uuid4())
    else:
        _check_password(password)
    encrypted_pwd = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode(
        "utf-8"
    )
    agency.password = encrypted_pwd
    agency.save()
    return dict(id=agency_id, password=password)


@list_func
def list_agency(
    page_no,
    page_size,
    id=None,
    agency_name=None,
    mch_id=None,
    parent_id=None,
    parent_name=None,
    enable=None,
    agency_type=None,
    return_many_to_many=False,
    use_vcode=None,
    working=None,
    use_white_ips=None,
    balance_gt=None,
    balance_lt=None,
    order_by=None,
    score=None,
    user_level=None,
):
    query = Account.objects.filter()
    if id:
        query = query.filter(pk=id)
    if agency_name:
        query = query.filter(user_name__contains=agency_name)
    if parent_name:
        query = query.filter(parent__user_name__contains=parent_name)
    if mch_id:
        query = query.filter(mchs__exact=mch_id)
    if parent_id:
        query = query.filter(parent_id=parent_id)
    if enable is not None:
        query = query.filter(enable=enable)
    if agency_type:
        query = query.filter(agency_type=agency_type.value)
    if use_vcode is not None:
        query = query.filter(use_vcode=use_vcode)
    if working is not None:
        query = query.filter(working=working)
    if use_white_ips is not None:
        query = query.filter(white_ips__isnull=(not use_white_ips))
    if balance_gt is not None:
        query = query.filter(balance__gt=balance_gt)
    if balance_lt is not None:
        query = query.filter(balance__lt=balance_lt)
    if score:
        query = query.filter(score__gt=score)
    if user_level:
        query = query.filter(
            Q(accountstat__pay_type_1_min_level=user_level)
            | Q(accountstat__pay_type_2_min_level=user_level)
            | Q(accountstat__pay_type_3_min_level=user_level)
            | Q(accountstat__pay_type_4_min_level=user_level)
            | Q(accountstat__pay_type_5_min_level=user_level)
        )
    if order_by:
        query = query.order_by(order_by)

    return (
        query.count(),
        [
            e.to_dict(exclude=["password"], return_many_to_many=return_many_to_many)
            for e in paginate(query, page_no, page_size)
        ],
    )


def update_agency(
    agency_id,
    avatar=None,
    use_vcode=None,
    warning_amount=None,
    remark=None,
    mch_ids=None,
    white_ips=None,
    enable=None,
    nickname=None,
    prologue=None,
    im_leaving_time=None,
    auto_show_level=None,
):
    assert agency_id
    assert im_leaving_time is None or im_leaving_time > 0
    with transaction.atomic():

        agency = _get_agency_account(agency_id)
        if avatar:
            agency.avatar = avatar
        if use_vcode is not None:
            agency.use_vcode = use_vcode
        if warning_amount is not None:
            agency.warning_amount = warning_amount
        if remark is not None:
            agency.remark = remark
        if nickname is not None:
            agency.nickname = nickname
        if prologue is not None:
            agency.prologue = prologue
        if mch_ids is not None:
            agency.mchs.clear()
            mch_service.bind_mch_to_agency(agency, mch_ids)
        if white_ips is not None:
            # 空字符串表示清空白名单，数据库必须存null
            white_ips = white_ips if white_ips else None
            agency.white_ips = white_ips
        if enable is not None:
            if enable:
                agency.enable = True
            else:
                agency.enable = False
                Token.objects.filter(user_id=agency_id).delete()
                Account.objects.filter(parent_id=agency_id).update(enable=False)
        if im_leaving_time:
            agency.im_leaving_time = im_leaving_time
        if auto_show_level is not None:
            assert isinstance(auto_show_level, UserLevel)
            agency.auto_show_level = auto_show_level.value
        agency.save()

    try:
        rpc_update_im_user_info(agency_id)
    except Exception:
        _LOGGER.exception("update im user info fail {}".format(agency_id))


@retry(RequestException, tries=2)
def rpc_update_im_user_info(agency_id):
    m_agency = _get_agency_account(agency_id)
    params = {
        "app_id": IMConf.app_id,
        "user_id": agency_id,
        "status": m_agency.enable,
        "nickname": m_agency.nickname if m_agency.nickname else None,
        "portrait_uri": (IMConf.img_host + m_agency.avatar)
        if m_agency.avatar
        else None,
        "im_leaving_time": m_agency.im_leaving_time,
    }
    resp = rpc.do_post(IMConf.host + "open/update_user_info/", params, IMConf.api_key)
    if resp["status"] != 0:
        raise Exception("fail update im user info: {}".format(agency_id))


def agency_go_to_work(agency_id):
    """
    代理上班
    :param agency_id:
    :return:
    """
    assert agency_id
    with transaction.atomic():
        agency = _get_agency_account(agency_id, with_lock=True)
        if agency.balance < 200000:
            raise BizException(
                ERROR_BALANCE_NOT_ENOUGH_GO_TO_WORK, int(agency.balance) / 100
            )

        agency.working = True
        agency.save()


def agency_go_off_work(agency_id, remark=None):
    """
    代理下班
    :return:
    """
    assert agency_id
    with transaction.atomic():
        agency = _get_agency_account(agency_id, with_lock=True)
        agency.working = False
        if remark:
            agency.remark = remark
        agency.save()


def list_online_agency(mch_id, user_id, recharge_success_amount=None, limit=20):
    """
    根据商户用户获取可用的代理列表

    算法公平性要点：
    - 兼顾每个代理的上线机会
    - 优质代理更多上线机会(优质标准：综合评分、收款多样性（信用卡，花呗）)
    - 新代理前面需要给试水考察的机会
    """
    if recharge_success_amount is not None:
        user_level = user_service.transform_user_level(recharge_success_amount)
    else:
        user = user_service.get_user_info(mch_id, user_id)
        user_level = user["level"]

    query = (
        Account.objects.filter(agency_type=AgencyType.NORMAL_AGENCY.value)
        .filter(
            enable=True,
            working=True,
            balance__gte=200000,
            accountstat__pay_types__isnull=False,
        )
        .filter(mchs__in=[mch_id])
    )

    # 黑名单过滤
    agencies_blocked = blacklist_service.get_agencies_by_user(mch_id, user_id)
    if agencies_blocked:
        query = query.exclude(id__in=agencies_blocked)

    # 过滤收款账户对用户要求等级要求
    # 只要任务一种支付方式满足要求，即展示代理
    query = query.filter(
        Q(accountstat__pay_type_1_min_level__lte=user_level)
        | Q(accountstat__pay_type_2_min_level__lte=user_level)
        | Q(accountstat__pay_type_3_min_level__lte=user_level)
        | Q(accountstat__pay_type_4_min_level__lte=user_level)
        | Q(accountstat__pay_type_5_min_level__lte=user_level)
    )

    agency_ids = agency_sort_service.get_sort_agency_ids_list(
        [q.id for q in query], limit
    )
    agencys = get_agencys_by_ids(agency_ids)
    random.shuffle(agencys)
    result = list()
    for act in agencys:
        stat = agency_stat_service.get_stat(act["id"])

        act["pay_types"] = _get_valid_pay_types(user_level, stat)

        act["support_auto_arrival"] = (
            stat["support_auto_arrival"]
            and stat["auto_arrival_min_level"] <= user_level
        )

        act["level"] = calculate_level_by_score(act["score"])
        # 实例avatar host
        act["avatar"] = IMConf.img_host + act["avatar"] if act["avatar"] else None
        act["month_orders_count"] = order_service.get_month_recharge(act["id"])
        act[
            "month_good_comments_count"
        ] = recharge_comment_service.get_month_good_comment_count(act["id"])
        result.append(act)
    return result


def _get_valid_pay_types(user_level, stat):
    pay_types = []
    for pt in stat["pay_types"]:
        if stat["pay_type_{}_min_level".format(pt)] <= user_level:
            pay_types.append(pt)
    return pay_types


def complaint_agency(
    agency_id, mch_id, user_id, complaint_types, images, complaint_content=None
):
    assert agency_id
    assert mch_id
    assert user_id
    assert complaint_types
    _LOGGER.info(
        "add_complaint_agency {},{},{},{},{},{}".format(
            agency_id, mch_id, user_id, complaint_types, complaint_content, images
        )
    )

    agency = get_agency_by_id(agency_id)
    if AgencyType(agency["agency_type"]) != AgencyType.NORMAL_AGENCY:
        raise BizException(ERROR_AGREE_TYPE, agency["agency_type"])

    if had_complain_today(agency_id, mch_id, user_id):
        raise BizException(ERROR_HAD_COMPLAIN_TODAY, agency_id)

    mch = mch_service.get_mch(mch_id)

    agency_complaint = Complaint()
    agency_complaint.agency_id = agency["id"]
    agency_complaint.mch_id = mch["id"]
    agency_complaint.user_id = user_id
    agency_complaint.complaint_types = [
        complaint_type.value for complaint_type in complaint_types
    ]
    agency_complaint.complaint_content = complaint_content
    agency_complaint.images = images
    agency_complaint.save()
    try:
        maestro_tracker.track_maestro_agency_complain(
            agency_complaint.id,
            int(datetime.strftime(datetime.now(), "%s")),
            agency_complaint.agency_id,
            agency_complaint.mch_id,
            agency_complaint.user_id,
            agency_complaint.complaint_types[0]
            if agency_complaint.complaint_types
            else None,
        )

        agency_sort_service.update_agency_score(
            agency_id, AgencySortScoreType.USER_COMPLAIN, agency_complaint.id
        )
    except Exception:
        _LOGGER.info(
            "track user_recharge_comment error {}".format(agency_complaint.id),
            exc_info=True,
        )
    return agency_complaint.to_dict()


@list_func
def list_complaint_agency(
    page_no,
    page_size,
    agency_id=None,
    agency_name=None,
    parent_id=None,
    parent_name=None,
    mch_id=None,
    user_id=None,
    complaint_content=None,
    complaint_types=None,
    created_at_begin=None,
    created_at_end=None,
    updated_at_begin=None,
    updated_at_end=None,
    has_done=None,
    order_by="-created_at",
):
    query = Complaint.objects.filter()
    if agency_id:
        query = query.filter(agency_id=agency_id)
    if agency_name:
        query = query.filter(agency__user_name__contains=agency_name)
    if parent_id:
        query = query.filter(agency__parent_id=parent_id)
    if parent_name:
        query = query.filter(agency__parent__user_name__contains=parent_name)
    if mch_id:
        query = query.filter(mch_id=mch_id)
    if user_id:
        query = query.filter(user_id=user_id)
    if complaint_content:
        query = query.filter(complaint_content__contains=complaint_content)
    if complaint_types:
        query = query.filter(complaint_types__contains=complaint_types)
    if created_at_begin:
        query = query.filter(created_at__gte=created_at_begin)
    if created_at_end:
        query = query.filter(created_at__lte=created_at_end)
    if updated_at_begin:
        query = query.filter(updated_at__gte=updated_at_begin)
    if updated_at_end:
        query = query.filter(updated_at__lte=updated_at_end)
    if has_done is not None:
        query = query.filter(has_done=has_done)
    if order_by:
        query = query.order_by(order_by)
    return query.count(), [e.to_dict() for e in paginate(query, page_no, page_size)]


def get_complain_by_id(complain_id):
    complain = _get_complain(complain_id)
    return complain.to_dict()


def handle_complain(complain_id, remark=None):
    assert complain_id
    with transaction.atomic():
        complain = _get_complain(complain_id, with_lock=True)
        if complain.has_done:
            raise BizException(ERROR_COMPLAIN_STATUS)
        complain.has_done = True
        complain.remark = remark
        complain.save()

    rpc_complain_handler(complain_id)


def had_complain_today(agency_id, mch_id, user_id):
    return Complaint.objects.filter(
        agency_id=agency_id,
        mch_id=mch_id,
        user_id=user_id,
        created_at__date=datetime.today(),
    )


def _get_complain(complain_id, with_lock=False):
    if with_lock:
        complain = Complaint.objects.select_for_update().filter(pk=complain_id).first()
    else:
        complain = Complaint.objects.filter(pk=complain_id).first()
    if not complain:
        raise BizException(ERROR_COMPLAIN_NOT_EXISTS)
    return complain


def calculate_level_by_score(score):
    """
    计算代理对应等级
    :return:
    """
    assert isinstance(AGENCY_SCORE_LEVEL, list)
    for score_line, level in sorted(AGENCY_SCORE_LEVEL, reverse=True):
        if score >= score_line:
            return level
    return 1


def calculate_score_by_level(level):
    """
    更具等级计算大于等于该等级的分数线
    :return:
    """
    assert isinstance(AGENCY_SCORE_LEVEL, list)
    assert level > 0
    for score_line, target_level in sorted(AGENCY_SCORE_LEVEL, reverse=True):
        if level >= target_level:
            return score_line


def calculate_agency_comment_star(agency_id):
    """
    计算代理订单评价数和评价平均分
    :return:
    """
    assert agency_id
    comment_count = Comment.objects.filter(agency_id=agency_id).count()
    comment_star = (
        Comment.objects.filter(agency_id=agency_id).all().aggregate(Avg("comment_star"))
    )
    return comment_count, comment_star


def get_agency_by_name(agency_name):
    assert agency_name
    user = Account.objects.filter(user_name=agency_name).first()
    if not user:
        raise BizException(ERROR_AGENCY_NOT_EXISTS)
    return user.to_dict(exclude=["password"])


def _check_password(password):
    errors = []
    if not any(x.isupper() for x in password):
        errors.append("必需含有大写字母\n")
    if not any(x.islower() for x in password):
        errors.append("必需含有小写字母\n")
    if not any(x.isdigit() for x in password):
        errors.append("必需含有数字\n")
    if not len(password) >= 6:
        errors.append("长度不少于6位\n")
    if errors:
        raise BizException(ILLEGAL_PARAMETER, "密码格式：" + "".join(errors))
